﻿using FFmpegVideoClip.Lib;
using PropertyChanged;
using SearchInFiles.Lib.Tools;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;


namespace SearchInFiles.Global
{
    public class FileElements:INotifyPropertyChanged
    {
        public class NotAvailableEncoding : Encoding
        {
            public override int GetByteCount(char[] chars, int index, int count) => throw new NotImplementedException();

            public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) => throw new NotImplementedException();

            public override int GetCharCount(byte[] bytes, int index, int count) => throw new NotImplementedException();

            public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) => throw new NotImplementedException();

            public override int GetMaxByteCount(int charCount) => throw new NotImplementedException();

            public override int GetMaxCharCount(int byteCount) => throw new NotImplementedException();
        }
        public enum GetFileModeEnum { OnlySelectRootDirectories, OnlySubDirectories, EnumerateSubDirectories }
        public static Dictionary<GetFileModeEnum, string> GetFileModeEnumChineseLanguage { get; private set; } =
            new() { { GetFileModeEnum.OnlySelectRootDirectories, "仅当前目录" }, { GetFileModeEnum.OnlySubDirectories, "仅子目录" }, { GetFileModeEnum.EnumerateSubDirectories, "枚举全部子目录" } };
        [AddINotifyPropertyChangedInterface] public record class EncodingData { public Encoding Value { get; private set; } public string Info { get; private set; } 
            public EncodingData(Encoding Value, string Info) { this.Value = Value; this.Info = Info; } }
        [AddINotifyPropertyChangedInterface]public class FileElement
        {
            public string Path { get; private set; }
            public string Directory { get; private set; }
            public string Name { get; private set; }
            public string ExtensionTyep { get; private set; }
            public EncodingData encodingData { get; set; }
            public bool Enable { get; set; }
            public bool EnableIFilter { get; set; }
            public bool IsSupport { get; set; }
            public bool IsSearchHit { get; set; }
            public bool IsTextType => Config.Instance.TextFileTypesSet.Contains(ExtensionTyep);
#pragma warning disable CS8618 // 在退出构造函数时，不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
            private FileElement() { IsSupport = true; IsSearchHit = false; }
#pragma warning restore CS8618 // 在退出构造函数时，不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
            public FileElement(string Path, string Directory, string Name, string ExtensionTyep, EncodingData encodingData, bool Enable = true, bool enableIFilter = true):this() {
                this.Path = Path; this.Directory = Directory; this.Name = Name; this.ExtensionTyep = ExtensionTyep; this.encodingData = encodingData; this.Enable = Enable; this.EnableIFilter = enableIFilter;}
            public FileElement(string Path, EncodingData encodingData, bool Enable = true , bool enableIFilter = true) : this()
            {
                if(!File.Exists(Path)) throw new FileNotFoundException($"文件不存在:[{Path}]"); 
                this.Path = Path;
                this.Directory = System.IO.Path.GetDirectoryName(Path)!;
                this.Name = System.IO.Path.GetFileNameWithoutExtension(Path);
                this.ExtensionTyep = System.IO.Path.GetExtension(Path);
                this.encodingData = encodingData;
                this.Enable = Enable;
                this.EnableIFilter = enableIFilter;
            }
        }
        public static EncodingData NotAvailableEncodingData { get; } = new(new NotAvailableEncoding(), "NotAvailable");
        public static ObservableCollection<EncodingData> EncodingCollection { get; } = new((new EncodingData[] { NotAvailableEncodingData, new(Encoding.Default, "Default") }).Concat(Encoding.GetEncodings()
            .Select(p => new EncodingData(p.GetEncoding(), p.Name))).Distinct(p => p.Info, StringComparer.OrdinalIgnoreCase).ToArray());
        public static Dictionary<Encoding, EncodingData> EncodingDataDic = EncodingCollection.ToDictionary(k => k.Value, v => v);

        private static FileElements? m_Instance;
        public static FileElements Instance => m_Instance ??= new();

        public event PropertyChangedEventHandler? PropertyChanged;

        private ObservableCollection<FileElement>? m_FileElementCollection;
        public ObservableCollection<FileElement> FileElementCollection
        {
            get => m_FileElementCollection ??= InitFileElementCollection();
            set
            {
                if (m_FileElementCollection == value) return;
                m_FileElementCollection!.CollectionChanged -= VerifyFileType;
                m_FileElementCollection = InitFileElementCollection(value);
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FileElementCollection)));
            }
        }
        public IEnumerable<FileElement> AllValidSelectFiles => FileElementCollection.Where(p => p.Enable && (Config.Instance.ValidTextExtensionSet.Contains(p.ExtensionTyep) || Config.Instance.ValidExtensionAllSet.Contains(p.ExtensionTyep)));
        //当前具有的类型种类(文本|非文本扩展)
        private bool m_IsExistTextFileType = false;
        public bool IsExistTextFileType 
        {
            get=>m_IsExistTextFileType; private set 
            {
                if(m_IsExistTextFileType == value) return;
                m_IsExistTextFileType = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsExistTextFileType)));
            }
        }
        private bool m_IsExistExtensionFileType = false;
        public bool IsExistExtensionFileType 
        {
            get => m_IsExistExtensionFileType;
            private set 
            {
                if (m_IsExistExtensionFileType == value) return;
                m_IsExistExtensionFileType = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsExistExtensionFileType)));
            } 
        }

        //按类型分化两类文件
        public IEnumerable<FileElement> AllExtensionTypeFile => FileElementCollection.Where(p => Config.Instance.ValidExtensionAllSet.Contains(p.ExtensionTyep));
        public IEnumerable<FileElement> AllTextTypeFile => FileElementCollection.Where(p => Config.Instance.ValidTextExtensionSet.Contains(p.ExtensionTyep));

        //确定当前文件具有的类型种类(文本|非文本扩展)
        private  void VerifyFileType(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if(sender==null) throw new ArgumentNullException(nameof(sender));
            ObservableCollection<FileElement> elements = (ObservableCollection<FileElement>)sender;
            bool TextType = false, ExtensionType = false;
            foreach (FileElement Item in elements)
            {
                if (!TextType && Config.Instance.ValidTextExtensionSet.Contains(Item.ExtensionTyep))
                    TextType = true;
                else if (!ExtensionType && Config.Instance.ValidExtensionAllSet.Contains(Item.ExtensionTyep))
                    ExtensionType = true;
                if (TextType && ExtensionType)
                    break;
            }
            IsExistTextFileType = TextType;
            IsExistExtensionFileType = ExtensionType;
        }
        private static ObservableCollection<FileElement> InitFileElementCollection(ObservableCollection<FileElement>? value = null)
        {
            if (value == null)
                value = new ObservableCollection<FileElement>();
            value.CollectionChanged += Instance.VerifyFileType;
            Instance.VerifyFileType(value, null!);
            return value;
        }

        private static EncodingData GetEncodingData(Encoding encoding)
        {
            if (EncodingDataDic.ContainsKey(encoding))
                return EncodingDataDic[encoding];
            return EncodingDataDic[Encoding.Default];
        }
        public async Task GetFilesAnsy(string RootPath, GetFileModeEnum Mode, CancellationTokenSource cancellationTokenSource, ProgressBar progressBar)
        {
            FileElementCollection.Clear();

            if (!Directory.Exists(RootPath))
                if (File.Exists(RootPath))
                    RootPath = System.IO.Path.GetDirectoryName(RootPath)!;
            if (!Directory.Exists(RootPath))
                throw new DirectoryNotFoundException($"路径 [{RootPath}] 不存在!");

            FileElementCollection = await Task.Run(() =>
            {
                string[] AllFile = Mode switch
                {
                    GetFileModeEnum.EnumerateSubDirectories => Directory.GetFiles(RootPath, "*", SearchOption.AllDirectories),
                    GetFileModeEnum.OnlySubDirectories => Directory.GetFiles(RootPath, "*", SearchOption.TopDirectoryOnly).Concat(
                                                          Directory.GetDirectories(RootPath, "*", SearchOption.TopDirectoryOnly).SelectMany(p =>
                                                          Directory.GetFiles(p, "*", SearchOption.TopDirectoryOnly))).ToArray(),
                    _ => Directory.GetFiles(RootPath, "*", SearchOption.TopDirectoryOnly)
                };

                HashSet<string> ValidExtensionAllSet = Config.Instance.ValidExtensionAllSet;
                HashSet<string> ValidTextExtensionSet = Config.Instance.ValidTextExtensionSet;

                ConcurrentBag<FileElement> elements = new ConcurrentBag<FileElement>();
                int Max = AllFile.Length;
                int current = 0;
                IProgress<int> progressHelp = null!;
                progressBar.Dispatcher.Invoke(() => { progressBar.Maximum = Max; progressHelp = new Progress<int>(p => progressBar.Value = p); });
                try
                {
                    UtilityTools.SetParallelMode(AllFile, cancellationTokenSource, 20, 8).AsUnordered().ForAll(p =>
                    {
                        progressHelp.Report(Interlocked.Increment(ref current));
                        string ext = System.IO.Path.GetExtension(p);
                        if (!ValidExtensionAllSet.Contains(ext))
                            return;
                        EncodingData encodingData = NotAvailableEncodingData;
                        if (ValidTextExtensionSet.Contains(ext))
                            encodingData = GetEncodingData(NChardet.DetectorGetEncoding.Detector(p, 2048));
                        elements.Add(new FileElement(p, System.IO.Path.GetDirectoryName(p)!, System.IO.Path.GetFileNameWithoutExtension(p), ext, encodingData));
                    });
                }
                catch (OperationCanceledException) {
                    return new ObservableCollection<FileElement>();}
                catch (Exception ex) 
                {
                    StringBuilder sb = new StringBuilder();
                    Exception? exception = ex;
                    while (exception != null)
                    {
                        sb.AppendLine(exception.ToString());
                        exception = exception.InnerException;
                    }
                    MessageBoxEx.Show(App.Current.MainWindow, sb.ToString(), "获取文件异常", MessageBoxButton.OK, MessageBoxImage.Error, MessageBoxResult.OK);
                    return new ObservableCollection<FileElement>();
                }
                
                if (cancellationTokenSource.IsCancellationRequested)
                    return new ObservableCollection<FileElement>();
                else
                    return new ObservableCollection<FileElement>(elements.OrderBy(p => p.Path));
            }).ConfigureAwait(false);
        }

    }
}
